Repository navigation
fix(resume): write inflight.json atomically - #2708
Conversation
The notes of a running turn were written in place: the writer truncated the file and then wrote the new bytes, so a crash in that window left it empty or half written, and the next start scheduled no pickup at all — every turn the crash cut off was dropped silently (#2705). Stage a temp name beside the file and rename it over the old one, so a crash at any point leaves either the previous complete notes or the new complete notes. A file that still cannot be parsed is named once with its path and the reason instead of passing for "nothing was in flight", and the first write of a host sweeps the temp names a dead predecessor left behind. Tests: core/test/resume-atomic.test.js runs the real install() in child processes and kills it with kill -9 inside the write; the previous writer left the target truncated and scheduled no pickup.
|
Verdict: PASS at e2afacd Ran at this tip: Reviewed at the tip tarball (6 files, +540/-27; no work-copy edits other than the red-first swap above, restored and
Tip checks: Residual notes, not blockers: (a) the sweep unlinks any |
Written-by: Dev
Fixes #2705
Owner: Dev
Reviewer: Reviewer
inflight.jsonwas written in place: the writer truncated the file and then wrote the new bytes, so a crash inside that window left an empty or half-written record and the next start scheduled no pickup at all — every turn the crash cut off was dropped silently. It is now staged as a temp name beside the file (<pid>.<random>.tmp, at the mode the replaced file already has,0600when the name is new) and renamed overinflight.json, so a crash at any point leaves either the previous complete notes or the new complete notes. A file that still cannot be parsed is named once with its path and the reason instead of passing for "nothing was in flight", and the first write of a host sweeps the temp names a dead predecessor left in the home.core/test/resume-atomic.test.jsruns the realinstall()in child processes in a temporary home and kills it with a realkill -9while the write is in flight. On the base tree the target is truncated (the window caught was{"target":0}) and the next start schedules nothing; with the change the target still holds the complete previous notes and the pickup is still scheduled ([3000]).Ran at e2afacd:
node scripts/ds-bot-patches.mjs generate(rewrotepatches/ds-bot/; only0001-core-fixes.patchand the newfiles/test/resume-atomic.test.jsdiffer frommain)node scripts/ds-bot-patches.mjs check→ ok, and… canonical→ ok (generateis a no-op)node core/scripts/build-client.mjs --check→ oknode scripts/lint-plugins.mjs→ 0 errorscd core && node scripts/lint.mjs→ oxlint 0 errors;arch-docsOK (run in a git checkout against this base, because the work copy is a tarball and has noorigin/main)node .github/scripts/changelog.mjs check,node .github/scripts/check-mojibake.mjs --check,node .github/scripts/testing-layers.mjs --check→ okcd core && TZ=UTC node --import ./test/harness-resolve.mjs --test test/resume-atomic.test.js test/follow-through.test.js test/private-files.test.js test/retired-bot-paths.test.js test/task-handoff-route.test.js test/readonly-window-reads.test.js test/connector-access.test.js test/project-board.test.js test/project-board-panels.test.js test/project-board-readonly.test.js test/project-board-view.test.js→ 201 tests, 0 failNot run: the full
npm run verify(left out on purpose per the task brief), the smoke test (noindex.jsor turn-path change), the plugin suites (nothing underplugins/changed), and the pack/typecheck steps of thepackagejob.Docs:
docs/subsystems/core-host.mdis updated (the private-files paragraph and the restart-pickup bullet), so noDocs-not-neededtrailer is needed.Checkpoint
{
"pr": 2708,
"claim": "Dev",
"issue": 2705,
"summary": "inflight.json was truncated in place before it was written, so a crash inside that window left an empty or half-written record and the next start scheduled no pickup at all. The writer now stages a temp name beside the file and renames it over the old one, so a crash leaves either the previous complete notes or the new complete ones. A file that still cannot be parsed is reported once with its path and reason, and a crashed write's temp name is swept by the next host that writes.",
"files": [
"core/src/host/resume.js",
"core/test/resume-atomic.test.js",
"docs/subsystems/core-host.md",
"changelog.d/2705-inflight-atomic.fixed.md",
"patches/ds-bot/0001-core-fixes.patch",
"patches/ds-bot/files/test/resume-atomic.test.js"
],
"checks": { "ran": "node scripts/ds-bot-patches.mjs check && node scripts/ds-bot-patches.mjs canonical && node core/scripts/build-client.mjs --check", "result": "green", "at": "e2afacd6343a01cc3f88d70cd92662be8aeace46" },
"next": "none — ready for review"
}